/*****************************************************************************
 * Copyright (c) 2010 CEA LIST.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *  Vincent Lorenzo (CEA LIST) vincent.lorenzo@cea.fr - Initial API and implementation
 *
 *****************************************************************************/
package org.eclipse.papyrus.uml.diagram.clazz.custom.utils;

import java.util.HashSet;
import java.util.Set;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.uml2.uml.Classifier;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Generalization;
import org.eclipse.uml2.uml.NamedElement;
import org.eclipse.uml2.uml.Property;
import org.eclipse.uml2.uml.Type;

public class GeneralizationUtil {

	/**
	 * Tests <code>true</code> if the classifier has this generalization in its inheritance links (directly or indirectly)
	 *
	 * @param generalization
	 *            a generalization
	 * @param el
	 *            a {@link Classifier} or a {@link Property}
	 * @return
	 *         <code>true</code> if the classifier has this generalization in its inheritance links (directly or indirectly) <code>false</code> if not
	 */
	public boolean isConcernedByGeneralizationChanges(Generalization generalization, View view) {
		boolean isConcerned = false;
		NamedElement graphicalOwner = getGraphicalOwner(view);
		// If the View element is owned by its graphical owner bypass the tests
		if ((Element) view.getElement().eContainer() != graphicalOwner) {
			if (graphicalOwner instanceof Classifier) {
				if (graphicalOwner != null && generalization != null) {
					isConcerned = getAllGeneralization((Classifier) graphicalOwner, null).contains(generalization);
				}
			} else if (graphicalOwner instanceof Property) {
				Type type = ((Property) graphicalOwner).getType();
				if (type instanceof Classifier) {
					isConcerned = getAllGeneralization((Classifier) type, null).contains(generalization);
				}
			}
			if (isConcerned && existsAnotherInheritanceWay((Element) view.getElement(), generalization, graphicalOwner, null)) {
				isConcerned = false;
			}
		}
		return isConcerned;
	}

	/**
	 * Tests if the classifier owns another way to inherit of the element
	 *
	 * @param inheritedElement
	 *            the inherited element
	 * @param forbiddenPath
	 *            the generalization that we are destroying
	 * @param el
	 *
	 * @return
	 *         <code>true</code> if another way exists to inherit of this element <code>false</code> if not
	 */
	protected boolean existsAnotherInheritanceWay(Element inheritedElement, Generalization forbiddenPath, NamedElement el, Set<Element> ignoredGeneralizations) {
		Set<Generalization> generalizations = new HashSet<>();
		Classifier _classifier = null;
		if (el instanceof Property) {
			Type type = ((Property) el).getType();
			if (type instanceof Classifier) {
				_classifier = (Classifier) type;
			}
		} else if (el instanceof Classifier) {
			_classifier = (Classifier) el;
		}
		// List Generalization that have already been tested to avoid loop issues
		// in case of Generalization cycles (such cycle creation should be avoided)
		Set<Element> ignoredGeneralizationsTmp = new HashSet<>();
		if (ignoredGeneralizations != null) {
			ignoredGeneralizationsTmp.addAll(ignoredGeneralizations);
		}
		if (el != null) {
			generalizations.addAll(_classifier.getGeneralizations());
			for (Generalization generalization : generalizations) {
				if ((generalization != forbiddenPath) && (!ignoredGeneralizationsTmp.contains(generalization))) {
					Classifier general = generalization.getGeneral();
					if (general != null) {
						ignoredGeneralizationsTmp.add(generalization);
						if (general.getOwnedMembers().contains(inheritedElement)) {
							return true;
						} else if (existsAnotherInheritanceWay(inheritedElement, forbiddenPath, general, ignoredGeneralizationsTmp)) {
							return true;
						}
					}
				}
			}
		}
		return false;
	}

	/**
	 * Returns the classifier owning the view
	 *
	 * @param view
	 *            a view
	 * @return
	 *         the Element owning this view
	 *         or <code>null</code> if not found
	 */
	protected NamedElement getGraphicalOwner(View view) {
		// the view is in a compartment
		if (view.eContainer() instanceof View) {
			EObject graphicalParent = ((View) view.eContainer()).getElement();
			if (graphicalParent instanceof Classifier || graphicalParent instanceof Property) {
				return (NamedElement) graphicalParent;
			}
		}
		return null;
	}

	/**
	 * Return all the generalization (direct and indirect) owning by the classifier
	 *
	 * @param classifier
	 *            a classifier
	 * @return
	 *         all the generalization (direct and indirect) owning by the classifier
	 */
	protected Set<Generalization> getAllGeneralization(Classifier classifier, Set<Classifier> alreadyParsedClassifiers) {
		Set<Generalization> generalizations = new HashSet<>();
		// Keep track of already parsed Classifiers to avoid loop in case
		// of Generalization cycle.
		Set<Classifier> parsedClassifiers = new HashSet<>();
		if (alreadyParsedClassifiers != null) {
			parsedClassifiers.addAll(alreadyParsedClassifiers);
		}
		Set<Generalization> generalizationsTmp = new HashSet<>();
		if (classifier != null) {
			generalizations.addAll(classifier.getGeneralizations());
			if (!parsedClassifiers.contains(classifier)) {
				parsedClassifiers.add(classifier);
				for (Generalization generalization : generalizations) {
					generalizationsTmp.addAll(getAllGeneralization(generalization.getGeneral(), parsedClassifiers));
				}
			}
		}
		generalizations.addAll(generalizationsTmp);
		return generalizations;
	}
}
